﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using Mapster;
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;

namespace Devonline.Core
{
    public static class CommonExtensions
    {
        /// <summary>
        /// 值类型直转
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static T To<T>(this IConvertible value)
        {
            try
            {
                return (T)Convert.ChangeType(value, typeof(T), CultureInfo.CurrentCulture);
            }
            catch
            {
                return default;
            }
        }
        /// <summary>
        /// 获取对象中属性的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static T GetPropertyValue<T>(this object obj, string propertyName)
        {
            if (obj == null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            return (T)obj.GetType().GetProperty(propertyName)?.GetValue(obj);
        }
        /// <summary>
        /// get value by property
        /// </summary>
        /// <param name="obj">The object that have the property</param>
        /// <param name="property">property name, support the format of "A.B.C"</param>
        /// <returns></returns>
        public static object GetPropertyValue(this object obj, string property)
        {
            if (obj != null && !string.IsNullOrEmpty(property))
            {
                if (obj.GetType().IsFromType<IEnumerable>())
                {
                    //TODO TBC 模式匹配改造
                    //(obj as ICollection).Cast<object>().ToList().ForEach(x => { x = x.GetPropertyValue(current); });
                    object[] array = (obj as IEnumerable).Cast<object>().ToArray();
                    for (int i = 0; i < array.Length; i++)
                    {
                        array[i] = array[i].GetPropertyValue(property);
                    }

                    return array;
                }
                else
                {
                    int position = property.IndexOf(AppSettings.CHAR_DOT, StringComparison.CurrentCulture);
                    string current = position > 0 ? property[..position] : property;
                    current = current.FirstCharToUpper();
                    PropertyInfo propertyInfo = obj.GetType().GetProperty(current);
                    if (propertyInfo != null && propertyInfo.CanRead)
                    {
                        obj = propertyInfo.GetValue(obj);
                        if (obj != null && position > 0)
                        {
                            string next = property[(position + 1)..];
                            obj = obj.GetPropertyValue(next);
                        }

                        return obj;
                    }
                }
            }

            return null;
        }
        /// <summary>
        /// 为对象的属性 property 设置 value 值
        /// </summary>
        /// <param name="obj">要设置属性的对象</param>
        /// <param name="property">属性名称</param>
        /// <param name="value">要设置的值</param>
        /// <returns>是否设置成功</returns>
        public static void SetPropertyValue(this object obj, string property, object value)
        {
            if (obj != null && property.IsNotNullOrEmpty())
            {
                property = property.FirstCharToUpper();
                var propertyInfo = obj.GetType().GetProperty(property);
                if (propertyInfo != null && propertyInfo.CanWrite)
                {
                    propertyInfo.SetValue(obj, (value != null && value is IConvertible) ? Convert.ChangeType(value, propertyInfo.PropertyType, CultureInfo.CurrentCulture) : value);
                }
            }
        }
        /// <summary>
        /// 数据写入缓存中
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="cache"></param>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public static void SetValue<T>(this IDistributedCache cache, string key, T value)
        {
            cache.SetString(key, value.ToJsonString());
        }
        /// <summary>
        /// 从缓存中读取值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="cache"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T GetValue<T>(this IDistributedCache cache, string key)
        {
            var value = cache.GetString(key);
            if (value == null)
            {
                return default;
            }

            return value.ToJsonObject<T>();
        }

        /// <summary>
        /// 对象转 json 字符串, 泛型用法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static string ToJsonString<T>(this T t) => JsonConvert.SerializeObject(t, AppSettings.JsonSerializerSettings);
        /// <summary>
        /// 对象转 json 字符串, 非泛型用法
        /// </summary>
        /// <param name="value"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string ToJsonString(this object value) => JsonConvert.SerializeObject(value, AppSettings.JsonSerializerSettings);

        /// <summary>
        /// 可以处理复杂映射
        /// 用法 source.Mapper(s => new Target { PropertyName = s.PropertyName });
        /// </summary>
        /// <typeparam name="TSource">输入类</typeparam>
        /// <typeparam name="TTarget">输出类</typeparam>
        /// <param name="expression">表达式目录树,可以为null</param>
        /// <param name="source">输入实例</param>
        /// <returns></returns>
        public static TTarget Mapper<TSource, TTarget>(this TSource source, Expression<Func<TSource, TTarget>> expression = null)
        {
            var memberBindingList = new List<MemberBinding>();
            var parameterExpression = Expression.Parameter(typeof(TSource), AppSettings.DEFAULT_LAMBDA_PARAMETER);
            if (expression != null)
            {
                parameterExpression = expression.Parameters[AppSettings.UNIT_ZERO];
                if (expression.Body != null)
                {
                    memberBindingList.AddRange((expression.Body as MemberInitExpression).Bindings);
                }
            }

            foreach (var item in typeof(TTarget).GetProperties())
            {
                if (typeof(TSource).GetProperty(item.Name) != null)
                {
                    var property = Expression.Property(parameterExpression, typeof(TSource).GetProperty(item.Name));
                    memberBindingList.Add(Expression.Bind(item, property));
                }
            }

            foreach (var item in typeof(TTarget).GetFields())
            {
                if (typeof(TSource).GetField(item.Name) != null)
                {
                    var property = Expression.Field(parameterExpression, typeof(TSource).GetField(item.Name));
                    memberBindingList.Add(Expression.Bind(item, property));
                }
            }

            var memberInitExpression = Expression.MemberInit(Expression.New(typeof(TTarget)), memberBindingList.ToArray());
            var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, new ParameterExpression[] { parameterExpression });
            return lambda.Compile().Invoke(source);
        }

        /// <summary>
        /// 复制对象
        /// </summary>
        /// <typeparam name="T">目标类型</typeparam>
        /// <param name="t">原始类型对象</param>
        /// <returns>复制得到的目标类型对象</returns>
        public static T Copy<T>(this T t) => t.Adapt<T>();
        /// <summary>
        /// 将原始对象复制到新的目标类型对象
        /// </summary>
        /// <param name="value">原始对象</param>
        /// <typeparam name="T">目标类型</typeparam>
        /// <returns></returns>
        public static T CopyTo<T>(this object value) => value.Adapt<T>();
        /// <summary>
        /// 将原始对象复制到新的目标类型对象
        /// </summary>
        /// <param name="value">原始对象</param>
        /// <typeparam name="T">目标类型</typeparam>
        /// <returns></returns>
        public static TTarget CopyTo<TSource, TTarget>(this TSource source) => source.Adapt<TSource, TTarget>();
        /// <summary>
        /// 将原始对象复制到新的目标类型对象
        /// </summary>
        /// <param name="source">原始对象</param>
        /// <param name="target">目标对象</param>
        /// <typeparam name="TSource">原始类型</typeparam>
        /// <typeparam name="TTarget">目标类型</typeparam>
        /// <returns></returns>
        public static TTarget CopyTo<TSource, TTarget>(this TSource source, TTarget target) => source.Adapt(target);
        /// <summary>
        /// 将原始对象复制到新的目标类型对象
        /// </summary>
        /// <param name="source">原始对象</param>
        /// <param name="targetType">目标对象类型</param>
        /// <returns></returns>
        public static object CopyTo(this object value, Type type) => value.Adapt(value.GetType().GetCoreType(), type);
        /// <summary>
        /// 将原始对象复制到新的目标类型对象
        /// </summary>
        /// <param name="source">原始对象</param>
        /// <param name="targetType">目标对象类型</param>
        /// <returns></returns>
        public static object CopyTo(this object source, object target) => source.Adapt(source.GetType().GetCoreType(), target.GetType().GetCoreType());
        /// <summary>
        /// 将目标对象列表中的属性值合并到源对象中
        /// </summary>
        /// <param name="source">原始对象</param>
        /// <param name="targets">待合并对象列表</param>
        /// <returns></returns>
        public static object Merge(this object source, params object[] targets)
        {
            if (source != null && targets.Length != 0)
            {
                var sourceType = source.GetType().GetCoreType();
                foreach (var target in targets)
                {
                    source.Adapt(target, sourceType, target.GetType().GetCoreType());
                }
            }

            return source;
        }

        /// <summary>
        /// 获取对象类型特性的指定属性值
        /// </summary>
        /// <param name="member">待获取对象</param>
        /// <param name="propertyName">用于获取值的特性上的属性名称</param>
        /// <returns>显示名称</returns>
        public static TValue GetAttributeValue<TAttribute, TValue>(this object obj, string propertyName) where TAttribute : Attribute
        {
            if (obj == null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            return obj.GetType().GetAttributeValue<TAttribute, TValue>(propertyName);
        }
        /// <summary>
        /// 获取对象类型成员特性的指定属性值
        /// </summary>
        /// <param name="member">待获取对象</param>
        /// <param name="memberName">成员名称, 可以是属性或者字段</param>
        /// <param name="propertyName">用于获取值的特性上的属性名称</param>
        /// <returns>显示名称</returns>
        public static TValue GetAttributeValue<TAttribute, TValue>(this object obj, string memberName, string propertyName) where TAttribute : Attribute
        {
            if (obj == null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            var member = obj.GetType().GetMember(memberName)?.FirstOrDefault();
            if (member != null)
            {
                return member.GetAttributeValue<TAttribute, TValue>(propertyName);
            }

            return default;
        }

        /// <summary>
        /// 获取对象的类型上可显示名字
        /// 可用于获取枚举值的可显示名字
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string GetDisplayName(this object obj) => obj?.GetType().GetDisplayName();
        /// <summary>
        /// 获取对象的属性上可显示名字
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="memberName">成员名称, 可以是属性或者字段</param>
        /// <returns></returns>
        public static string GetDisplayName(this object obj, string memberName) => obj?.GetType().GetDisplayName(memberName);
        /// <summary>
        /// 获取对象上的隐射到数据库的字段名字(Column 特性的 Name 值)
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="memberName">成员名称, 可以是属性或者字段</param>
        /// <returns></returns>
        public static string GetColumnName(this object obj, string memberName) => obj?.GetType().GetMember(memberName)?.FirstOrDefault()?.GetColumnName();
        /// <summary>
        /// 获取对象上的隐射到数据库的表名(Table 特性的 Name 值)
        /// </summary>
        /// <returns></returns>
        public static string GetTableName(this object obj) => obj?.GetType()?.GetTableName();
        /// <summary>
        /// 获取对象设置的 json 
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="memberName"></param>
        /// <returns></returns>
        public static string GetJsonPropertyName(this object obj, string memberName) => obj?.GetType()?.GetJsonPropertyName(memberName);

        /// <summary>
        /// 获取时间差的详情, 精确到 kind 指定值
        /// </summary>
        /// <param name="timeSpan"></param>
        /// <returns></returns>
        public static string GetTimeDetail(this TimeSpan timeSpan, TimeKind kind = TimeKind.MilliSecond)
        {
            var result = new List<string>();
            if (timeSpan.Days > 0 && kind >= TimeKind.Day)
            {
                if (timeSpan.Days > AppSettings.UNIT_DAYS_A_YEAR && kind >= TimeKind.Year)
                {
                    int diff = timeSpan.Days % AppSettings.UNIT_DAYS_A_YEAR;
                    result.Add($"{timeSpan.Days / AppSettings.UNIT_DAYS_A_YEAR} 年");
                    if (diff > 0)
                    {
                        result.Add($"{diff} 天");
                    }
                }
                else
                {
                    result.Add($"{timeSpan.Days} 天");
                }
            }

            if (timeSpan.Hours > 0 && kind >= TimeKind.Hour)
            {
                result.Add($"{timeSpan.Hours} 小时");
            }

            if (timeSpan.Minutes > 0 && kind >= TimeKind.Minute)
            {
                result.Add($"{timeSpan.Minutes} 分钟");
            }

            if (timeSpan.Seconds > 0 && kind >= TimeKind.Second)
            {
                result.Add($"{timeSpan.Seconds} 秒");
            }

            if (timeSpan.Milliseconds >= 0 && kind >= TimeKind.MilliSecond)
            {
                result.Add($"{timeSpan.Milliseconds} 毫秒");
            }

            return string.Join(AppSettings.CHAR_SPACE, result);
        }
        /// <summary>
        /// 获取相对于当前时间时间差的详情, 精确到 kind 指定值
        /// </summary>
        /// <param name="dateTime"></param>
        /// <param name="kind"></param>
        /// <returns></returns>
        public static string GetTimeDetail(this DateTime dateTime, TimeKind kind = TimeKind.MilliSecond) => (DateTime.Now - dateTime).GetTimeDetail(kind);

        /// <summary>
        /// get inner exception message of a exception
        /// </summary>
        /// <param name="ex">Exception instance</param>
        /// <returns>inner exception message</returns>
        public static string GetInnerException(this Exception ex)
        {
            if (ex == null)
            {
                throw new ArgumentNullException(nameof(ex));
            }

            if (ex.InnerException != null)
            {
                return ex.InnerException.GetInnerException();
            }

            return ex.Message;
        }
        /// <summary>
        /// 错误信息和堆栈一起返回
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        public static string GetMessage(this Exception ex) => $"错误信息: { ex.GetInnerException() }, 错误堆栈: { ex.StackTrace }";

        /// <summary>
        /// 反射调用对象的泛型方法
        /// </summary>
        /// <param name="caller">调用对象</param>
        /// <param name="methodName">方法名</param>
        /// <param name="parameters">参数列表</param>
        /// <param name="returnValue">返回值</param>
        /// <param name="genericTypes">泛型类型</param>
        public static void InvokeGenericMethod(this object caller, string methodName, object[] parameters, out object returnValue, params Type[] genericTypes)
        {
            if (caller == null)
            {
                throw new ArgumentNullException(nameof(caller));
            }

            returnValue = null;
            var methodInfo = caller.GetType().GetMethods().FirstOrDefault(x => x.IsGenericMethod && x.Name == methodName && x.GetParameters().Length == (parameters?.Length ?? 0));
            if (methodInfo != null)
            {
                returnValue = methodInfo.MakeGenericMethod(genericTypes).Invoke(caller, parameters);
            }
        }
        /// <summary>
        /// 反射调用对象的泛型方法
        /// </summary>
        /// <typeparam name="T">返回值类型</typeparam>
        /// <param name="caller">调用对象</param>
        /// <param name="methodName">方法名</param>
        /// <param name="parameters">参数列表</param>
        /// <param name="genericTypes">泛型类型</param>
        /// <returns>返回调用结果</returns>
        public static T InvokeGenericMethod<T>(this object caller, string methodName, object[] parameters, params Type[] genericTypes) where T : class
        {
            if (caller == null)
            {
                throw new ArgumentNullException(nameof(caller));
            }

            var methodInfo = caller.GetType().GetMethods().FirstOrDefault(x => x.IsGenericMethod && x.Name == methodName && x.GetParameters().Length == (parameters?.Length ?? 0));
            if (methodInfo != null)
            {
                return (T)methodInfo.MakeGenericMethod(genericTypes).Invoke(caller, parameters);
            }

            return default;
        }
        /// <summary>
        /// 反射调用对象 obj 中非公开属性 fieldName 类型为 T 的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="fieldName"></param>
        /// <returns></returns>
        public static T GetNonPublicValue<T>(this object obj, string fieldName = null)
        {
            if (obj == null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            var fieldInfo = obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic)?.FirstOrDefault(x => x.Name == fieldName || x.FieldType.IsFromType<T>());
            if (fieldInfo != null)
            {
                return (T)fieldInfo.GetValue(obj);
            }

            return default;
        }

        /// <summary>
        /// get string includes variables value from string variables expression
        /// usage (eg in user model):
        /// string value = "this is template string that contains Name: {Name} and Department: {Department.Name}";
        /// value = user.GetVariablesExpression(value);
        /// then value will be "this is template string that contains Name: Alex and Department: Huatek"
        /// </summary>
        /// <param name="obj">which object will be calculated to get value</param>
        /// <param name="value">which value expression will be calculated</param>
        /// <param name="prefix">veriable prrfox, default is "{"</param>
        /// <param name="suffix">veriable suffix, default is "}"</param>
        /// <param name="separator">veriable separator, default is "."</param>
        /// <returns></returns>
        public static string GetVariablesExpression(this object obj, string value, string prefix = "{", string suffix = "}", string separator = ".")
        {
            if (value.IsNotNullOrEmpty() && value.Contains(prefix, StringComparison.InvariantCultureIgnoreCase) && value.Contains(suffix, StringComparison.InvariantCultureIgnoreCase))
            {
                string exp = prefix + @"(\w+" + separator + "?)+?" + suffix;
                return Regex.Replace(value, exp, new MatchEvaluator(match =>
                {
                    var result = string.Empty;
                    if (match.Value.Length > prefix.Length + suffix.Length)
                    {
                        var propertyName = match.Value.Replace(prefix, string.Empty, StringComparison.InvariantCultureIgnoreCase).Replace(suffix, string.Empty, StringComparison.InvariantCultureIgnoreCase);
                        var value = obj.GetPropertyValue(propertyName);
                        if (value != null)
                        {
                            result = value switch
                            {
                                string str => str,
                                DateTime dateTime => dateTime.ToString(AppSettings.DEFAULT_DATETIME_FORMAT, CultureInfo.CurrentCulture),
                                DateTimeOffset dateTimeOffset => dateTimeOffset.ToString(AppSettings.DEFAULT_DATETIME_FORMAT, CultureInfo.CurrentCulture),
                                IEnumerable enumerable => enumerable.Cast<object>().ToList().ToString<object>(),
                                _ => value.ToString(),
                            };
                        }
                    }

                    return result;
                }), RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
            }

            return string.Empty;
        }
    }
}
